package sweet.helper

import java.util.Date
import java.util.Calendar
import java.text.SimpleDateFormat

/**
 * The companion of JDate class. 
 */
object JDate{
  val MILLIS_IN_SEC = 1000
  val MILLIS_IN_MIN = 60 * MILLIS_IN_SEC
  val MILLIS_IN_HOUR = 60 * MILLIS_IN_MIN
  val MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR
  
 // ==================== 
 // Note that java.text.SimpleDateFormat is not ThreadSafe, so it's important to use "def" instead of val 
 // when return a new instance object whem adding more methods to this class.
 // ====================
 
  /** yyyy-MM-dd */
  val DATE_FMT = "yyyy-MM-dd"
  /** HH:mm:ss */
  val TIME_FMT = "HH:mm:ss"
  /** yyyy-MM-dd HH:mm:ss */
  val DATETIME_FMT = "yyyy-MM-dd HH:mm:ss"
  
  /** Create new DateFormat instance using DATE_FMT. */
  def dateDf = new SimpleDateFormat(DATE_FMT)
  /** Create new DateFormat instance using TIME_FMT. */
  def timeDf = new SimpleDateFormat(TIME_FMT)
  /** Create new DateFormat instance using DATETIME_FMT. */
  def datetimeDf = new SimpleDateFormat(DATETIME_FMT)
  
  /** Call #now */
  def apply() = now  
  /** Call #mkdate */
  def apply(dt: String) = mkdate(dt)
  /** Call #mkFrom */
  def apply(dt: String, fmt: String) = mkdateFrom(dt, fmt)
  /** Call #mkdate */
  def apply(year: Int, month: Int, date: Int) = mkdate(year, month, date)
  /** Call #mkdatetime */
  def apply(year: Int, month: Int, date: Int, hour: Int, min: Int, sec: Int) = 
    mkdatetime(year, month, date, hour, min, sec)
    
  /** Create new timestamp/date instance of JDate that wraps java.util.Date */
  def now = new JDate(new Date) //today's datetime
  /** Same as now.dateDf */
  def today = now.datePart //today's date part only
  /** Same as now.timePart */
  def time = now.timePart //today's time part only.  
  /** return yesterday's date part */
  def yesterday = new JDate(today.millis - MILLIS_IN_DAY)
  
  /** Create a date with DATE_FMT parts ONLY. */
  def mkdate(dt: String) = new JDate(dateDf.parse(dt))
  /** Create a date with TIME_FMT parts ONLY. */
  def mktime(dt: String) = new JDate(timeDf.parse(dt))
  /** Create a date with DATETIME_FMT parts ONLY. */
  def mkdatetime(dt: String) = new JDate(datetimeDf.parse(dt))
  
  /** Create a date using given numberical parameters */
  def mkdate(year: Int, month: Int, date: Int) = new JDate(dateDf.parse(year+"-"+month+"-"+date))
  /** Create a date using given numberical parameters */
  def mktime(hour: Int, min: Int, sec: Int) = new JDate(timeDf.parse(hour+":"+min+":"+sec))
  /** Create a date using given numberical parameters */
  def mkdatetime(year: Int, month: Int, date: Int, hour: Int, min: Int, sec: Int) = 
    new JDate(datetimeDf.parse(year+"-"+month+"-"+date+" "+hour+":"+min+":"+sec))
    
  /** Create a date using java.text.SimpleDateFormat format string. */
  def mkdateFrom(dt: String, fmt: String) = new JDate(new SimpleDateFormat(fmt).parse(dt))
   
  /** implicit converter from Date to JDate */
  implicit def date2jdate(dt: Date) = new JDate(dt)
}

/** A wrapper to java.util.Date that provide convinient methods to handle date and time. */
class JDate(val date: Date){
  import JDate._
  
  /** Construct a JDate from millis value.*/
  def this(millis: Long) = this(new Date(millis))
    
  /** Return an instance of default calendar with time set to this date. */ 
  def calendar: Calendar = {
    val cal = Calendar.getInstance
    cal.setTime(date)
    cal
  }
    
  def addMillis(amount: Long) = new JDate(millis + amount)
  def addSecs(amount: Int) = new JDate(millis + amount * MILLIS_IN_SEC)
  def addMins(amount: Int) = new JDate(millis + amount * MILLIS_IN_MIN)
  def addHours(amount: Int) = new JDate(millis + amount * MILLIS_IN_HOUR)
  def addDays(amount: Int) = new JDate(millis + amount * MILLIS_IN_DAY)
    
  /** Return the sunday on the week of this date. */
  def sunday: JDate = {
    calendar.get(Calendar.DAY_OF_WEEK) match {
      case Calendar.SUNDAY => this   
      case Calendar.MONDAY => this.addDays(-1)   
      case Calendar.TUESDAY => this.addDays(-2)   
      case Calendar.WEDNESDAY => this.addDays(-3)   
      case Calendar.THURSDAY => this.addDays(-4)   
      case Calendar.FRIDAY => this.addDays(-5)   
      case Calendar.SATURDAY => this.addDays(-6)   
    }
  }
  
  /** return String representation of Weekday */
  def weekday: String = {
    calendar.get(Calendar.DAY_OF_WEEK) match {
      case Calendar.SUNDAY => "Sunday"   
      case Calendar.MONDAY => "Monday" 
      case Calendar.TUESDAY => "Tuesday"
      case Calendar.WEDNESDAY => "Wednesday"   
      case Calendar.THURSDAY => "Thursday"
      case Calendar.FRIDAY => "Friday"
      case Calendar.SATURDAY => "Saturday"
    }
  }  
  
  /** Compare to a JDate object. */
  def compareTo(that: JDate) = this.date.compareTo(that.date)
  
  /** Compare to a Date object. */
  def compareTo(that: Date) = this.date.compareTo(that)
  
  /** return millis seconds of instance of given date. */
  def millis = date.getTime
  /** return seconds of instance of given date. */
  def secs = (date.getTime / MILLIS_IN_SEC).toInt
  /** return mins of instance of given date. */
  def mins = (date.getTime / MILLIS_IN_MIN).toInt
  /** return hours of instance of given date. */
  def hours = (date.getTime / MILLIS_IN_HOUR).toInt
  /** return a formated date string of instance of given date. (See java.text.SimpleDateFormat) */
  def format(fmt: String) = new SimpleDateFormat(fmt).format(date)
  
  /** return date portion of a date object */
  def datePart: JDate = { val df = dateDf; new JDate(df.parse(df.format(date))) }
  /** return time portion of a date object */
  def timePart: JDate = { val df = timeDf; new JDate(df.parse(df.format(date))) }
  
  /** return DATE_FMT string */
  def dateString = { dateDf.format(date) }
  /** return TIME_FMT string */
  def timeString = { timeDf.format(date) }
  /** return DATETIME_FMT string */
  def datetimeString = { datetimeDf.format(date) }
  
  /** We can compare either a JDate or Date object */
  override def equals(that: Any):Boolean = {
    if(that.isInstanceOf[JDate])
      date == that.asInstanceOf[JDate].date
    else if(that.isInstanceOf[Date])
      date == that.asInstanceOf[Date]
    else
      throw new Exception("We can only compare to JDate or Date object.")
  }
  /** Use underlying date.hashCode. */
  override def hashCode = date.hashCode
}
